<template>
    <div class="all-backgrond"></div>
    <div class="camelia-socket-page">
        <!-- 头部按钮和标题等信息 -->
        <div id="header-btns-and-info">
            <div id="app-title">
                <span>snow app</span>
            </div>
            <!-- AI icons -->
            <div class="Ai-chat-icons">
                <AiChat />
            </div>
            <!-- <el-button @click="InsertingWinding" style="-webkit-app-region: drag;">close Websocket</el-button> -->
            <div id="functions-btns">
                <el-icon color="black" size="20" @click="disposeWindow('minWin')">
                    <Minus />
                </el-icon>
                <el-icon color="black" size="20" @click="disposeWindow('maxWin')">
                    <FullScreen />
                </el-icon>
                <el-icon color="black" size="20" @click="disposeWindow('editWin')">
                    <CircleClose />
                </el-icon>
            </div>
        </div>
        <!-- 侧栏菜单 -->
        <div class="aside-snow-menus">
            <GeneralMenu @menu-click="menuClick" :group-menus="groupOptions" />
        </div>
        <div id="show-friend-list">
            <Contact @child-click="getFriendInfoFromChild" @dispose-menu="menuClick" :group-menus="groupOptions"
                @toggleFriendMenu="handleToggleFriendMenu" :grouped-menus="groupedOptions" />
        </div>
        <!-- 淘汰的动态class -->
        <!-- :style="{ 'width': leftMenuVisible ? chatMinAreaWidth : chatMaxAreaWidth, 'left': leftMenuVisible ? chatLeft : '0' }" -->
        <div id="content-main">
            <!-- 用户对话区域 -->
            <div id="user-chat-area">
                <div id="header-menus" class="flex items-center justify-around"
                    v-show="targetChatFriend.uu_id.trim() !== '' || targetChatGroup.group_id.trim() !== ''">
                    <!-- 显示当前聊天信息 -->
                    <div id="current-chat-info" class="flex items-center justify-around text-center">
                        <div class="user-info"
                            v-if="disposeChatType === 'userChat' && targetChatFriend.uu_id.trim() !== ''">
                            <p>{{ filterUserRemark || targetChatFriend.username }}</p>
                            <!-- <span>{{ targetChatFriend.gex || "未知" }}</span> -->
                        </div>
                        <div class="group-info"
                            v-else-if="disposeChatType === 'groupChat' && targetChatGroup.group_id.trim() !== ''"
                            @click="showGroupDrawerInfo = true">
                            <p>{{ targetChatGroup.group_name }}</p>
                            <n-button size="large" style="width: 40px;" ghost :bordered="false">
                                <template #icon>
                                    <img src="https://img.icons8.com/?size=100&id=85501&format=png&color=000000" alt="">
                                </template>
                            </n-button>
                            <!-- <p>{{ targetChatGroup.group_id }}</p> -->
                        </div>
                    </div>
                    <!-- 一言 -->
                    <!-- <div id="saying">
                        <p>人生自古谁无死，早死晚死都得死。---吕子乔</p>
                    </div> -->
                </div>
                <!-- 展示聊天内容 -->
                <div id="who-to-chat-with-content">
                    <DisposeMessage :chat-message-type="disposeChatType" :group-menus="groupOptions"
                        @send-changeFriend-info="sendChangeFriendInfo" :connected-websocket="websocket!" />
                </div>
                <!-- 发送信息区域       这里先不删除 -->
                <div id="chat-send-message" class="flex">
                    <!-- 聊天按钮 -->
                    <div id="chat-area">
                        <!-- 聊天信息区域 -->
                        <div id="messages" v-show="targetChatFriend.uu_id !== '' || targetChatGroup.group_id != ''">
                            <!-- 聊天内容区域 -->
                            <div id="chat-content-area">
                                <n-popover :show="showPopover" :keep-alive-on-hover="false" :raw="true"
                                    style="max-height: 200px;max-width: 200px;width: 180px;height: 150px;" scrollable>
                                    <template #trigger>
                                        <n-input v-model:value="chatInfo.content" type="text" placeholder="开始聊天啦~"
                                            ref="chatInputRef" size="large" round clearable maxlength="30" show-count
                                            style="width: 600px;"
                                            @keydown.enter.exact.prevent="sendCommonMessage(websocket as WebSocket, messageType)"
                                            @keydown="handleAtTagDelete" />
                                    </template>
                                    <div id="group-member-at-list" ref="groupMemberListRef" tabindex="0"
                                        @keydown.down.prevent="handleAtListKey(EventType.DownEvent)"
                                        @keydown.up.prevent="handleAtListKey(EventType.UpEvent)"
                                        @keydown.enter="handleAtListKey(EventType.EnterEvent)">
                                        <div id="member-items" v-for="(memberItem, memberItemIndex) in members"
                                            :key="memberItemIndex"
                                            :class="{ memberAtSelected: selectedIndex === memberItemIndex }"
                                            style="height: 30px;margin: 5px;border-radius: 4px;"
                                            @click="groupMemberAtListClick(memberItemIndex, memberItem)">
                                            <div id="info" style="width: 100%;height: 100%;" class="flex items-center">
                                                <img v-if="memberItem.avatar" :src="memberItem.avatar"
                                                    :alt="memberItem.label"
                                                    style="width: 40px;height: 100%;float: left;">
                                                <span
                                                    style="display: block;width: 100%;height: 100%;text-align: center;line-height: 27px;
                                                    text-overflow: ellipsis;text-wrap: nowrap;overflow: hidden;font-size: 15px;">{{
                                                        memberItem.label }}</span>
                                            </div>
                                        </div>
                                    </div>
                                </n-popover>
                                <div id="tail-btns">
                                    <div class="emoji-btns">
                                        <V3Emoji :recent="true" @click-emoji="handleEmojiClick"
                                            :options-name="optionsName" :fulldata="false" :keep="true">
                                            <div class="my-btn">
                                                <n-button ghost size="large">
                                                    <template #icon>
                                                        <img :src="ExpressionImage" alt="emoji-image" />
                                                    </template>
                                                </n-button>
                                            </div>
                                        </V3Emoji>
                                    </div>
                                    <n-button v-for="(functionItem, functionItemIndex) in chatFunctionMenus"
                                        :key="functionItemIndex" size="large"
                                        @click="handleChatMenuChange(functionItem.key)">
                                        <template #icon>
                                            <img :src="functionItem.icon" alt="">
                                        </template>
                                    </n-button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 展示菜单组件 -->
    <Subassembly :menuInfo="propMenuInfo" style="position: absolute;" />

    <!-- 在 HTML 中添加一个隐藏的 <input type="file"> 元素 -->
    <input type="file" id="hidden-file-input"
        accept="image/*, .pdf, .doc, .docx, .txt, .csv, .xls, .xlsx, .ppt, .pptx, .odt, .ods, .odp, .rtf, .md"
        ref="fileUploadInput" style="display: none;" @change="handleFileInputChange" />

    <n-modal v-model:show="showModal">
        <div class="stock-info-list"
            style="width: 500px;height: 550px;background-color: white;border-radius: 5px;position: absolute;transform: translate(65%,-5%);">
            <n-card size="small" title="聊天记录">
                <n-input v-model:value="stockFilterInfo" round placeholder="输入信息">
                    <template #suffix>
                        <n-icon :component="FlashOutline" />
                    </template>
                </n-input>
                <n-tabs type="card" tab="Oasis" animated>
                    <n-tab-pane name="stock-message-info" tab="消息">
                        <n-infinite-scroll :distance="10">
                            <div id="text-info-items"
                                v-for="(textItem, textItemIndex) in filterStockInfoList.textMessages"
                                :key="textItemIndex">
                                <!-- 基本信息 -->
                                <figure class="common-text-info flex items-center justify-around">
                                    <n-image :src="textItem.userNickAvatar" size="small" lazy alt="用户头像" width="35px"
                                        height="30px" />
                                    <div class="message-content">
                                        <figcaption>{{ textItem.userNickName }}</figcaption>
                                        <figcaption>{{ textItem.message }}</figcaption>
                                    </div>
                                </figure>
                                <div id="send-time">
                                    <p>{{ convertToLocalTime(textItem.timeStamp) }}</p>
                                </div>
                            </div>
                            <n-empty description="快活跃起来把~" v-if="!parsedMessage.textMessages.length"></n-empty>
                        </n-infinite-scroll>
                    </n-tab-pane>
                    <n-tab-pane name="stock-file-info" tab="文件">
                        <n-infinite-scroll :distance="5">
                            <div id="file-info-items"
                                v-for="(fileItem, fileItemIndex) in filterStockInfoList.fileMessages"
                                :key="fileItemIndex">
                                <figure class="file-info">
                                    <n-image :src="fileItem.userNickAvatar" lazy size="small" width="35px" height="30px"
                                        style="border-radius: 4px;" />
                                    <div class="message-content flex justify-between">
                                        <div class="basic-info">
                                            <figcaption>{{ fileItem.userNickName }}</figcaption>
                                            <img v-if="JSON.parse(fileItem.message).mimeType.trim().startsWith('image/')"
                                                :src="JSON.parse(fileItem.message).fileUrl"></img>
                                            <figcaption v-else>{{ JSON.parse(fileItem.message).fileName }}</figcaption>
                                            <figcaption>{{ prettySize(JSON.parse(fileItem.message).fileSize) }}
                                            </figcaption>
                                        </div>
                                        <div class="send-time">
                                            <p>{{ convertToLocalTime(fileItem.timeStamp) }}</p>
                                        </div>
                                    </div>
                                </figure>
                            </div>
                            <n-empty description="聊天信息为空！" v-if="!parsedMessage.fileMessages.length"></n-empty>
                        </n-infinite-scroll>
                    </n-tab-pane>
                </n-tabs>
            </n-card>
        </div>
    </n-modal>

    <!-- 在这里添加一个群聊的侧栏信息 -->
    <n-drawer v-model:show="showGroupDrawerInfo" :width="300">
        <n-drawer-content :title="targetChatGroup.group_name" closable :native-scrollbar="false">
            <!-- 过滤框框、群成员列表（头像+用户名） -->
            <div id="group-members-list">
                <div class="header-btns flex items-center justify-evenly">
                    <n-input v-model:value="filterGroupMembers" style="width: 200px;" clearable>
                        <template #suffix>
                            <n-icon :component="FlashOutline"></n-icon>
                        </template>
                    </n-input>
                    <n-button :bordered="false" ghost size="large" style="width:40px;"
                        @click="showAddGroupMembersStatus = true">
                        <template #icon>
                            <img src="https://img.icons8.com/?size=100&id=95841&format=png&color=000000" alt="">
                        </template>
                    </n-button>
                </div>
                <div id="list">
                    <div id="member-list-items" v-for="(memberItem, memberItemIndex) in filteredMembersInfo"
                        :key="memberItemIndex"
                        v-show="filteredMembersInfo?.length! > 0 && filteredMembersInfo !== undefined">
                        <div>
                            <n-avatar :src="memberItem.avatar"
                                style="height: 40px !important;width: 40px !important;" />
                            <span>{{ memberItem.label }}</span>
                        </div>
                    </div>
                    <n-empty description="没有符合条件的群成员"
                        v-show="filteredMembersInfo?.length! == 0 || filteredMembersInfo === undefined"></n-empty>
                </div>
            </div>
            <!-- 群聊基本信息（群聊名称、群公告、群备注（仅自己可见）、聊天内容） -->
            <div id="group-basic-info">
                <!-- 这里使用n-form表单完成 -->
                <n-form :model="formRef" ref="formRef">
                    <n-form-item v-for="item in changeGroupInfo" :key="item.key" :label="item.label">
                        <template v-if="!item.editable">
                            <p @click="toggleEditable(item)" style="color: rgba(0,0,0,0.5);">{{ item.data.trim() ?
                                item.data : (item.key ===
                                    'group-nickname') ? targetChatGroup.group_name : userStore().userInfo.username }}</p>
                        </template>
                        <template v-else>
                            <n-input v-model:value="item.changeInfo" @blur="toggleEditable(item)" :bordered="false" />
                        </template>
                    </n-form-item>
                </n-form>
            </div>
            <!-- 底部功能按钮（清空聊天记录、退出群聊） -->
            <div id="group-footer-dispose-btns">
                <div id="clear-group-info-btn"
                    @click="chatStore().clearGroupInfoByGroupId(targetChatGroup.group_id, 'clear-chat-message')">
                    <n-button type="error" dashed color="#8a2be2">清空聊天记录</n-button>
                </div>
                <div id="edit-group-btn" @click="showQuitDialog = true">
                    <n-button type="error" dashed color="#ff69b4">退出群聊</n-button>
                </div>
            </div>
        </n-drawer-content>
    </n-drawer>

    <!-- 显示退出群聊的el-dialog信息（这个更美观） -->
    <el-dialog v-model="showQuitDialog" close-icon="false" width="250px" :with-header="false" top="40vh">
        <div id="quit-group-info" style="display: flex;flex-direction: column;">
            <div class="flex flex-col items-center">
                <p>将退出群聊“{{ targetChatGroup.group_name }}”</p>
                <el-checkbox label="清除聊天记录"></el-checkbox>
            </div>
            <div class="btns flex align-center justify-center" style="margin-top: 10px;">
                <el-button type="danger" @click="sendQuitGroupMessage(websocket as WebSocket, {
                    messageType: 'quit-group-chat', groupId: targetChatGroup.group_id, memberId: userStore().userInfo.uu_id, groupName: targetChatGroup.group_name
                })">确定</el-button>
                <el-button type="primary" @click="showQuitDialog = false">取消</el-button>
            </div>
        </div>
    </el-dialog>

    <!-- 展示拉取群成员信息的组件 -->
    <AddMemberDialog :currentGroupId="targetChatGroup.group_id" v-show="showAddGroupMembersStatus"
        :showStatus="showAddGroupMembersStatus" :connectWebsocket="websocket"
        @update:showAddGroupMembersStatus="showAddGroupMembersStatus = $event" />


</template>

<script lang="ts" setup>
import { onMounted, ref, provide, watch, nextTick, computed, reactive, h } from 'vue';
import { validToken, getFriendChatMessage, getGroupChatMessage, getOffLineMessageInfo, getOffLineGroupMessage } from "@utils/axiosApi";
import {
    chatInfo, handleChatWithObject, optionsName, handleEmojiClick, chatFunctionMenus, handleChatMenuClick, targetChatFriend,
    propMenuInfo, showComponent, resetChunkFileInfo, targetChatGroup, disposeGroupChat, sendCommonMessage, messageType, AtMemberIds, convertToLocalTime,
    groupOptions, changeGroupInfo, toggleEditable
} from "@utils/socketPageService"
import { connectWebSocket } from "@utils/webSocketService"
import V3Emoji from "vue3-emoji"
import 'vue3-emoji/dist/style.css'
import AiChat from "@views/AI/AiChat.vue"
import DisposeMessage from "@/factories/Message/disposeMessage.vue"
import ExpressionImage from "@images/icons/expression.png"
import { handleSmallFile, handleChunkFile, generateFixedLengthKey } from '@utils/fileService';
import Subassembly from "@components/Subassembly/subassembly.vue"
import { ElMessage, ElMessageBox } from 'element-plus';
import { userStore } from '@store/userStore';
import Contact from "@components/General/Contact/index.vue"
import GeneralMenu from "@components/Menu/menu.vue"
import { ChatList, FriendMenuList, MembersInfo, StockGroupMessage, StockPrivateMessage, TargetChatType, UserFriendInfo } from '@/types/types';
import { useFactory } from '../../utils/factory';
import { chatStore } from '@/store/chatStore';
import { friendStore } from '@/store/friendStore';
import { FlashOutline } from '@vicons/ionicons5'
import { FormInst, NButton, NList, NListItem, NScrollbar, NThing, useMessage, useNotification } from 'naive-ui';
import { sendQuitGroupMessage } from '@/utils/websocketMessage/sendMessage';
import AddMemberDialog from "@/factories/AddMemberDialog/AddMemberDialog.vue"

const emits = defineEmits(['child-click']);


// Ai动态图标
const AiImage = ref<string>("https://free.picui.cn/free/2025/08/16/68a07fef27d96.png");

const friend_store = friendStore();


// 处理窗口事件
function disposeWindow(windowType: string) {
    switch (windowType) {
        case "minWin":      // 窗口最小化
            window.electronAPI.minimizeWindow();
            break;
        case "maxWin":     // 窗口最大化
            ElMessage.warning("暂不支持！暂不支持！");
            break;
        case "editWin":         // 关闭窗口
            ElMessageBox.confirm('确认退出程序呒', 'Warning', {
                confirmButtonText: 'OK', cancelButtonText: 'Cancel', type: 'warning',
                beforeClose: (action, instance, done): void => {
                    if (action === 'confirm') {
                        instance.confirmButtonLoading = true,
                            instance.confirmButtonText = "Saving...",
                            setTimeout(() => {
                                done(),
                                    setTimeout(() => {
                                        instance.confirmButtonLoading = false,
                                            window.electronAPI.editWindow();
                                    }, 500);
                            }, 2000);
                    } else if (action === 'cancel') {
                        done();
                    }
                }
            })
            break;
    }
}

// 根据groupOptions的value动态生成分组之后的好友列表（其实就是根据用户好友列表中的好友对象的group属性进行分组操作）
const groupedOptions = computed(() => {
    // 为每个分组生成子菜单项
    const options = groupOptions.map((option: any) => ({
        label: option.label,        // 分组名称
        value: option.value,         // 分组value
        expanded: option.expanded,
        children: friend_store.storageUserFriendList.filter(
            (friend: UserFriendInfo) => friend.group === option.value   // 筛选出属于该分组的好友
        ).map((friend: UserFriendInfo): any => ({
            avatar: friend.friendDetails.avatar,
            signature: friend.friendDetails.signatureOfTheIndividual,
            label: friend.remark || friend.friendDetails.username,
            value: friend.friendDetails.uu_id
        }))
    }))
    return options;
})
// 处理子组件修改数据事件
function handleToggleFriendMenu(item: FriendMenuList) {
    const index = groupOptions.findIndex((option: FriendMenuList) => option.value === item.value);
    if (index !== -1) {
        groupOptions[index].expanded = !groupOptions[index].expanded;
    }
}



// websocket
let websocket = ref<WebSocket | null>(connectWebSocket());


const showPopover = ref<boolean>(false);

// 监听消息框内容
watch(() => chatInfo.content, (newValue: string): void => {
    // 条件守卫
    if (chatInfo.messageType === TargetChatType.Private) return;
    // 去掉首尾空格再判断
    const val = newValue.trim();
    // 匹配单个@，或以@结尾的字符
    showPopover.value = /^@$|@$/.test(val);
})



// 控制消息类型展示的变量
const disposeChatType = ref<string>("userChat");
async function getFriendInfoFromChild(data: ChatList, chatType?: string): Promise<void> {
    disposeChatType.value = chatType!
    showComponent.value = false;
    // ----------------------------__!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1在这里获取缓存信息
    chatType === 'userChat' ? handleChatWithObject(data) : disposeGroupChat(data);
}
provide('shareDataMethod', getFriendInfoFromChild);


const prettySize = (bytes: number) => {
    if (bytes < 1024) return bytes + ' B'
    const k = 1024
    const sizes = ['KB', 'MB', 'GB']
    let i = Math.floor(Math.log(bytes) / Math.log(k))
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i - 1]
}


const fileUploadInput = ref<HTMLElement | null>(null);
// 处理文件选择后的逻辑（上传文件后的逻辑）
// 定义常量
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 最大文件大小
const CHUNK_SIZE = 3 * 1024 * 1024; // 分页文件大小
const CHUNK_HEADER = new Uint8Array([70, 67, 72, 75]); // "FCHK"
const SMALL_FILE_HEADER = new Uint8Array([83, 77, 76, 70]); // "SMLF"

// 文件头信息构造函数
function createFileHeader(fileType: Uint8Array, fileKey: string): Uint8Array {
    const FILE_HEADER_SIZE = 4 + 20;    // 文件类型（4字节）+ fileKey（20字节）
    const fileHeader = new Uint8Array(FILE_HEADER_SIZE);
    // 写入文件类型
    fileHeader.set(fileType, 0);
    // 写入fileKey
    const fileKeyBytes = new TextEncoder().encode(fileKey.padEnd(20, '\0'));
    fileHeader.set(fileKeyBytes, 4);
    return fileHeader;
}
// 文件元数据构建函数
async function handleFileInputChange(event: Event): Promise<void> {
    const result = await useFactory();
    const input = event.target as HTMLInputElement;
    const file = input.files ? input.files[0] : null;

    if (!file) {
        ElMessage.error("未选择文件");
        return;
    }
    if (!websocket.value) {
        ElMessage.error("WebSocket未建立");
        return;
    }
    ElMessage.success("获取上传的文件了")
    // 构建文件元数据信息对象
    const fileMetadataMap = {
        messageType: "file-metadata",
        fromUserId: result.user_store?.userInfo.uu_id,
        userNickName: userStore().userInfo.username,
        userNickAvatar: userStore().userInfo.avatar,
        fileName: file.name,
        // fileType: new Uint8Array(0),
        // mimeType: isPhotoFile(file.name),
        toChatId: targetChatFriend.uu_id || targetChatGroup.group_id,
        fileKey: generateFixedLengthKey(file.name, 20),
        fileSize: file.size,
        fileReceiverType: messageType.value === TargetChatType.Private ? 'private-chat-file-message' : 'group-chat-file-message'
    }
    // 清空对象中原有的数据
    resetChunkFileInfo();

    const fileName = file.name;
    const isChunked = file.size > MAX_FILE_SIZE;
    const totalChunks = isChunked ? Math.ceil(file.size / CHUNK_SIZE) : 1;

    // 构建store存储文件信息对象
    const storeFileInfo = {
        message: "",
        receiverId: targetChatFriend.uu_id,
        senderId: userStore().userInfo.uu_id,
        timeStamp: new Date().toISOString()
    }
    // friendStore().storageMessage(targetChatUser.value.uu_id, storeFileInfo, true);

    try {
        if (isChunked) {
            // fileMetadataMap.fileType = CHUNK_HEADER;
            // 创建文件头信息
            const fileHeader = createFileHeader(CHUNK_HEADER, fileMetadataMap.fileKey);
            handleChunkFile(websocket.value, file, fileMetadataMap, totalChunks);
        } else {
            // fileMetadataMap.fileType = SMALL_FILE_HEADER;
            // 创建文件头信息
            const fileHeader = createFileHeader(SMALL_FILE_HEADER, fileMetadataMap.fileKey);
            handleSmallFile(websocket.value, file, fileMetadataMap, fileHeader, storeFileInfo)
        }
    } catch (error) {
        ElMessage.error(`处理上传文件出现错误：${error}`);
    }

}
// 判断是否为照片文件
// function isPhotoFile(fileName: string): string {
//     // 定义支持的照片文件扩展名
//     const photoExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"];
//     // 提取文件扩展名
//     const fileExtension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
//     return photoExtensions.includes(fileExtension) ? `image/${fileExtension.slice(1)}` : "unknown"; // 在返回扩展名的时候去掉.
// }

// 二次封装函数
function handleChatMenuChange(key: string) {
    const result = handleChatMenuClick(key);
    if (result === 'uploadFile') {
        if (fileUploadInput.value) {
            fileUploadInput.value.click();      // 模拟点击事件
        }
    } else if (result === "searchAction") {
        showModal.value = true;
    } else if (result === 'announcementAction') {
        announcementClick();
    }
}


const messageNaive = useMessage();
const notification = useNotification();
// n-announcement
function announcementClick() {
    let markAsRead = false;
    const n = notification.create({
        title: "项目开发公告",
        description: "一位准大一学生开发、一种兴趣爱好",
        content: () => h(NScrollbar, { style: { maxHeight: "200px" } }, () => h(NList, { bordered: true, hoverable: true, clickable: true }, [
            h(NListItem, null, {
                default: () => h(NThing, { title: "项目初期" }, {
                    description: () => [
                        h('div', {}, "emmmmm好像也不知道该说点啥，也算是完成了一个小阶段吧，基本的功能基本都完成了（说个废话~），剩下的慢慢优化慢慢做咯，求别喷"),
                        h('div', { style: { textAlign: 'right' } }, '2025-1-1')
                    ]
                })
            }),
            h(NListItem, null, {
                default: () => h(NThing, {
                    title: "公告2", description: "测试内容 B，具体说明-----"
                })
            }),
            h(NListItem, null, {
                default: () => h(NThing, {
                    title: "公告3", description: "测试内容 C，具体说明-----"
                })
            }),
            h(NListItem, null, {
                default: () => h(NThing, {
                    title: "公告4", description: "测试内容 D，具体说明-----"
                })
            })
        ]),),
        meta: "2025-7-31 20:21",
        avatar: () => h('img', {
            src: 'https://free.picui.cn/free/2025/08/01/688c07c364277.jpeg'
        }),
        action: () =>
            h(NButton, {
                text: true, type: 'primary',
                onClick: () => {
                    markAsRead = true
                    n.destroy()
                }
            },
                {
                    default: () => '已读'
                }),
        onClose: () => {
            if (!markAsRead) {
                messageNaive.warning("请设为已读!");
                return false;
            }
        }
    })
}


let timer: ReturnType<typeof setTimeout> | null = null;
watch([() => chatInfo.messageType, () => targetChatGroup.group_id], ([newType, newGroup]) => {
    // 手写模拟极简防抖
    // 每次变化先清掉上一次的计时器
    if (timer) {
        clearTimeout(timer);
        timer = null;
    }
    if (newType === TargetChatType.Group && newGroup) {
        const groupId = targetChatGroup.group_id;
        timer = setTimeout((): void => {
            // 1、先读取本地信息        不需要从localStorage中读取数据了，因为pinia-plugin-persistedstate已经将数据同步
            const members = chatStore().storageGroupMembersList[groupId];
            // 2、为空才会发送
            if (!members || members.length === 0) {
                // 三秒内无任何变化     向websocket发送进入群聊信息
                const websocketChatInfo = {
                    content: "加入群聊!",
                    toChatId: userStore().userInfo.uu_id,
                    messageType: "join-group",
                    groupId: groupId
                }
                try {
                    websocket.value?.send(JSON.stringify(websocketChatInfo));
                } catch (error) {
                    ElMessage.error(`出现错误：${error}`);
                }
            }
            return;
        }, 500)
    }
})

const members = useGroupMembers()
// 封装一个带有“@全体成员”的getter
function useGroupMembers() {
    return computed(() => {
        const realList = chatStore().storageGroupMembersList[targetChatGroup.group_id] || [];
        return [
            {
                key: 'all',
                label: "全体成员",
                avatar: ''
            },
            ...realList
        ]
    })
}
let groupMemberListRef = ref<HTMLElement | null>(null);
// 添加事件监听器
watch(showPopover, (value: boolean): void => {
    if (value) {
        nextTick((): void => {
            // 确保元素存在
            groupMemberListRef.value = document.getElementById('group-member-at-list');
            groupMemberListRef.value?.focus();
        })
    }
})
// 事件枚举
enum EventType {
    DownEvent = 'snow-down-event',
    UpEvent = 'snow-up-event',
    EnterEvent = 'snow-enter-event'
}
function handleAtListKey(keyType: string) {
    const list = members.value || [];
    if (list.length === 0) return;
    if (keyType === EventType.DownEvent) {
        selectedIndex.value = (selectedIndex.value! + 1) % list.length;
    } else if (keyType === EventType.UpEvent) {
        selectedIndex.value = (selectedIndex.value! - 1 + list.length) % list.length;
    } else {
        const member = list[selectedIndex.value!];
        if (!member) return;
        groupMemberAtListClick(selectedIndex.value!, member);
    }
}

// 群成员at列表选中状态
const selectedIndex = ref<number | null>(0);
const chatInputRef = ref<HTMLElement | null>(null);
// 群聊成员点击事件（向at列表添加）
function groupMemberAtListClick(index: number, member?: MembersInfo) {
    if (!member) return;

    const ZW = '\u200B';
    const isAll = member.key === 'all';

    /* 1. 如果输入框里已经有 @all，则任何 @ 都不能再插入（包括再次点 @all）*/
    const hasAll = new RegExp(`${ZW}@全体成员${ZW}`).test(chatInfo.content);
    if (hasAll) {
        // ElMessage.warning('已存在 @全体成员，无法再添加其他 @');
        showPopover.value = false;
        chatInputRef.value?.focus();
        chatInfo.content = chatInfo.content.endsWith('@') ? chatInfo.content.slice(0, -1) : chatInfo.content;
        return;
    }

    /* 2. 如果这次选的是 @all，则先把其他 @xxx 全部清空 */
    if (isAll) {
        chatInfo.content = chatInfo.content.replace(/\u200B@\S+\u200B/g, '');
        AtMemberIds.value.length = 0;
    }

    /* 3. 以下为你的原逻辑 … */
    const tagText = `${ZW}@${member.label}${ZW} `;
    const token = tagText;
    const raw = chatInfo.content;

    // 普通成员的去重检测（@all 已在上一步清空，无需再检）
    if (!isAll) {
        const existingRE = new RegExp(`${ZW}@${member.label}${ZW}`);
        if (existingRE.test(raw)) {
            showPopover.value = false;
            chatInfo.content = raw.endsWith('@') ? raw.slice(0, -1) : raw;
            chatInputRef.value?.focus();
            return;
        }
    }

    const before = raw.endsWith('@') ? raw.slice(0, -1) : raw;
    chatInfo.content = before + token;
    selectedIndex.value = index;

    if (!AtMemberIds.value.includes(member.key)) {
        AtMemberIds.value.push(member.key);
    }

    nextTick(() => {
        const el = (chatInputRef.value as any)?.$el.querySelector('input') as HTMLInputElement;
        if (!el) return;
        const pos = before.length + token.length;
        el.setSelectionRange(pos, pos);
        el.focus();
    });

    showPopover.value = false;
}
/** 返回光标所在的 token {start,end}，无则 null */
function findToken(val: string, cursor: number) {
    // 1. 只编译一次：匹配“零宽空格 + @ + 非空白 + 零宽空格 + 空格”
    const TOKEN_RE = /\u200B@[^\s]+\u200B /g;
    TOKEN_RE.lastIndex = 0;
    let m;
    while ((m = TOKEN_RE.exec(val)) !== null) {
        const start = m.index;
        const end = start + m[0].length;
        // 包含尾部空格
        if (cursor > start && cursor <= end) {
            // 提取@label 中的label部分
            const label = m[0].replace(/\u200B/g, '').replace(/^@/, '').trim();
            return { start, end, label };
        }
    }
    return null;
}
/** 删除 @label 时同步删除 AtMemberIds 中对应 key */
function handleAtTagDelete(e: KeyboardEvent) {
    // 只关心这四个键
    if (!['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(e.key)) return;

    const el = (chatInputRef.value as any)?.$el?.querySelector('input') as HTMLInputElement;
    if (!el) return;

    const val = el.value;
    const cursor = el.selectionStart ?? 0;
    const token = findToken(val, cursor);
    if (!token) return;        // 不在 token 内，保持原生行为

    e.preventDefault();        // 以下都要拦截默认动作

    switch (e.key) {
        case 'Backspace':
        case 'Delete':
            // 1、根据label拿到member
            const member = chatStore().memberMap(targetChatGroup.group_id).get(token.label);
            if (member) {
                // 2、在AtMemberIds移除
                AtMemberIds.value = AtMemberIds.value.filter(id => id !== member.key);
            }
            console.log(AtMemberIds.value);
            // 3、删除输入框中的token
            chatInfo.content = val.slice(0, token.start) + val.slice(token.end);
            nextTick(() => {
                el.setSelectionRange(token.start, token.end);
                el.focus();
            });
            break;
        case 'ArrowLeft':
            el.setSelectionRange(token.start, token.start);
            break;
        case 'ArrowRight':
            el.setSelectionRange(token.end, token.end);
            break;
    }
    nextTick(() => el.focus());
}

const loadedIds = new Set();        // 用户记录已经加载过的ID
// 通过监听好友信息、群聊信息的变化来加载缓存信息           // 优化之后的函数
async function getStockMessaesAndWatch(userId: string, groupId: string | null, isGroup: boolean) {
    const id = isGroup ? `group-${groupId}` : `user-${userId}`;
    let message = {
        messageType: "get-cache-information-success",
        currentUserid: userStore().userInfo.uu_id,
        targetId: isGroup ? targetChatGroup.group_id : targetChatFriend.uu_id,
        chatType: !isGroup ? "friend-stock" : "group-stock"            // friend or group
    };
    if (!loadedIds.has(id)) {
        loadedIds.add(id);

        let data: any[] = [];
        data = isGroup ? await getOffLineGroupMessage(userStore().userInfo.uu_id, groupId!) : await getOffLineMessageInfo(userStore().userInfo.uu_id, userId);
        if (data && data.length > 0) {
            const storeMethod = isGroup ? chatStore().addGroupMessage : chatStore().addPrivateMessage;
            storeMethod(data);
            // 动态添加属性
            try {
                if (websocket.value && websocket.value.readyState === WebSocket.OPEN) {
                    websocket.value.send(JSON.stringify(message));
                }
            } catch (error) {
                console.error(error);
            }
        }
    }
}
watch([() => targetChatFriend.uu_id, () => targetChatGroup.group_id], async ([newUserId, newGroupId]): Promise<void> => {
    if (newUserId) {
        await getStockMessaesAndWatch(newUserId, null, false);
    } else if (newGroupId) {
        await getStockMessaesAndWatch('', newGroupId, true);
    }
}, { deep: true })



// 展示用户、群聊详细信息的n-modal
const showModal = ref(false);

watch(showModal, async (newModal: boolean): Promise<void> => {
    if (newModal) {
        if (messageType.value === 'unidirectional') {
            await getFriendChatMessage(userStore().userInfo.uu_id, targetChatFriend.uu_id);
        } else if (messageType.value === 'Multi-directional') {
            await getGroupChatMessage(targetChatGroup.group_id);
        }
    }
})


// 过滤消息列表
const stockFilterInfo = ref<string>("");
// 解析消息（划分为文本消息和文件消息）
const parsedMessage = computed(() => {
    let targetId;
    let messages;
    const textMessages: StockPrivateMessage[] | StockGroupMessage[] = [];
    const fileMessages: StockPrivateMessage[] | StockGroupMessage[] = [];
    // 根据 messageType 确定是私聊还是群聊，并获取相应的 targetId 和消息数组
    if (messageType.value === TargetChatType.Private) {
        targetId = targetChatFriend.uu_id;
        messages = chatStore().stockFriendMessage[targetId] || [];
    } else {
        targetId = targetChatGroup.group_id;
        messages = chatStore().stockGroupMessage[targetId] || [];
    }
    // 处理每条消息
    messages.forEach((msg: any) => {
        try {
            // 根据消息的 type 字段分类
            if (msg.type === 'FILE') {
                const parsedContent = parseIfJSON(msg.message) || parseIfJSON(msg.content);
                if (parsedContent && parsedContent.fileName) {
                    fileMessages.push(msg);
                }
            } else if (msg.type === 'TEXT') {
                textMessages.push(msg);
            }
        } catch (error) {
            console.error('Error parsing message:', error);
        }
    });

    return { textMessages, fileMessages };
})
function parseIfJSON(jsonString: any): any {
    try {
        return JSON.parse(jsonString);
    } catch {
        return null;
    }
}
// 过滤聊天信息
const filterStockInfoList = computed(() => {
    if (!stockFilterInfo.value) return parsedMessage.value;     // 如果stockFilterInfo为空，则直接返回parsedMessage.value中的原始信息

    // 如图stockFilterInfo不为空，则进行信息过滤
    const filteredTextMessages = parsedMessage.value.textMessages.filter((msg: StockPrivateMessage | StockGroupMessage) => {
        return msg.message && (
            (msg.message.includes(stockFilterInfo.value)) || (msg.timeStamp.includes(stockFilterInfo.value)) ||
            (msg.userNickName!.includes(stockFilterInfo.value)) || (msg.messageKey && msg.messageKey.includes(stockFilterInfo.value))
        );
    })

    const filteredFileMessages = parsedMessage.value.fileMessages.filter((msg: StockPrivateMessage | StockGroupMessage) => {
        const parsedMsg = parseIfJSON(msg.message);
        return parsedMessage &&
            (
                (parsedMsg.fileName && parsedMsg.fileName.includes(stockFilterInfo.value) || (msg.timeStamp && msg.timeStamp.includes(stockFilterInfo.value) ||
                    (msg.senderId && msg.senderId.includes(stockFilterInfo.value) || (msg.userNickName && msg.userNickName.includes(stockFilterInfo.value)))
                ))
            );
        // return JSON.parse(msg.message).filterName.includes(stockFilterInfo.value);       像这种简单的判断条件被淘汰了，应该使用上面的复杂判断条件（保证了数据的可读性）
    })

    return {
        textMessages: filteredTextMessages,
        fileMessages: filteredFileMessages
    }
})

// 通过计算属性获得用户备注信息
const filterUserRemark = computed((): string | undefined => {
    return friend_store.storageUserFriendList.find((friend: UserFriendInfo) => {
        return friend.friendDetails.uu_id === targetChatFriend.uu_id;
    })?.remark;
})
// 子组件向websocket发送修改备注、群组信息的事件处理器
function sendChangeFriendInfo(infoType: string, info: string, changeInfo: { remark: string | null, group: string | null }) {
    const friendId = targetChatFriend.uu_id;
    // 通过friendId来修改临时变量中的信息
    const friend = friend_store.getFriendListFromUserId(friendId);
    if (!friend) {
        ElMessage.error("未找到用户！");
        return;
    }
    if (changeInfo.remark !== null) {
        friend.remark = changeInfo.remark!;
    } else if (changeInfo.group !== null) {
        friend.group = changeInfo.group!;
    } else {
        console.error("未提供有效的修改信息！");
        return;
    }
    // 1、参数校验
    if (!friendId || !infoType || !info) {
        console.error('Invalid parameters:', { friendId, infoType, info });
        return;
    }
    // 2、修改临时变量信息
    // 构建消息对象
    const changeInfoObj = {
        messageType: 'change-friend-info',
        friendId: friendId,
        infoType: infoType,
        infoContent: info
    }
    console.log(typeof changeInfoObj.infoContent)
    try {
        if (websocket.value && websocket.value.readyState === WebSocket.OPEN) {
            websocket.value.send(JSON.stringify(changeInfoObj));
        }
    } catch (error) {
        console.error(error);
    }
}

function menuClick(url: string, menuKey: string | undefined) {
    if (url === "ASIDE-MENU-MUSIC") {
        emits('child-click', '/snow-music');
        return;
    }
    showComponent.value = true;
    // switchVisible();
    propMenuInfo.currentMenu = url;
    propMenuInfo.currentMenuKey = menuKey ?? "";
    propMenuInfo.randomKey = Math.random();     // 添加随机值确保响应式更新
}

// 展示群聊侧栏信息（群成员列表、群名称、群公告）
const showGroupDrawerInfo = ref<boolean>(false);

// 过滤群成员信息的变量、计算属性
const filterGroupMembers = ref<string>("");
const filteredMembersInfo = computed((): { key: string; label: string; avatar: string; }[] | undefined => {
    if (!filterGroupMembers.value) {
        return members.value.filter(member => member.key !== 'all')
    } else {
        return members.value.filter((member) => {
            return member.key !== 'all' && (member.label.toLocaleLowerCase().includes(filterGroupMembers.value) || member.key.toLocaleLowerCase().includes(filterGroupMembers.value));
        });
    }
});


// 显示群聊详细信息的n-form表单
const formRef = ref<FormInst | null>(null);

// 退出群聊时显示的dialog
const showQuitDialog = ref<boolean>(false);
const showAddGroupMembersStatus = ref<boolean>(false);



onMounted((): void => {
    // 用户身份验证
    validToken();
    // connectionStompSockJS();
})

</script>

<style scoped lang="scss">
@import url("./socket.scss");
</style>